{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "319adfec-2d0a-49f2-87f9-275c4a32add2",
      "metadata": {
        "id": "319adfec-2d0a-49f2-87f9-275c4a32add2"
      },
      "source": [
        "# Streaming\n",
        "\n",
        "## Review\n",
        "\n",
        "In module 2, covered a few ways to customize graph state and memory.\n",
        "\n",
        "We built up to a Chatbot with external memory that can sustain long-running conversations.\n",
        "\n",
        "## Goals\n",
        "\n",
        "This module will dive into `human-in-the-loop`, which builds on memory and allows users to interact directly with graphs in various ways.\n",
        "\n",
        "To set the stage for `human-in-the-loop`, we'll first dive into streaming, which provides several ways to visualize graph output (e.g., node state or chat model tokens) over the course of execution."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "db024d1f-feb3-45a0-a55c-e7712a1feefa",
      "metadata": {
        "id": "db024d1f-feb3-45a0-a55c-e7712a1feefa"
      },
      "outputs": [],
      "source": [
        "%%capture --no-stderr\n",
        "%pip install --quiet -U langgraph langchain_google_genai langgraph_sdk"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "70d7e41b-c6ba-4e47-b645-6c110bede549",
      "metadata": {
        "id": "70d7e41b-c6ba-4e47-b645-6c110bede549"
      },
      "source": [
        "## Streaming\n",
        "\n",
        "LangGraph is built with [first class support for streaming](https://langchain-ai.github.io/langgraph/concepts/low_level/#streaming).\n",
        "\n",
        "Let's set up our Chatbot from Module 2, and show various way to stream outputs from the graph during execution."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "5b430d92-f595-4322-a56e-06de7485daa8",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "5b430d92-f595-4322-a56e-06de7485daa8",
        "outputId": "8593bf84-2dfa-4c02-a7f9-1c3ed6adda5a"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "env: GOOGLE_API_KEY=AIzaSyDojJx56O8nzFauBKc_rj_0o7wvAnQgHAI\n",
            "AIzaSyDojJx56O8nzFauBKc_rj_0o7wvAnQgHAI\n"
          ]
        }
      ],
      "source": [
        "from google.colab import userdata\n",
        "\n",
        "%env GOOGLE_API_KEY = {userdata.get('GEMINI_API_KEY')}\n",
        "\n",
        "import os\n",
        "print(os.environ[\"GOOGLE_API_KEY\"])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "9b2a5d0a",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "9b2a5d0a",
        "outputId": "ee4db27a-470d-44ea-9747-1da067c589fa"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "env: LANGCHAIN_API_KEY=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\n"
          ]
        }
      ],
      "source": [
        "%env LANGCHAIN_API_KEY = {userdata.get('LANGCHAIN_API_KEY')}\n",
        "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
        "os.environ[\"LANGCHAIN_PROJECT\"] = \"langchain-academy\""
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4d0682fc",
      "metadata": {
        "id": "4d0682fc"
      },
      "source": [
        "Note that we use `RunnableConfig` with `call_model` to enable token-wise streaming. This is [only needed with python < 3.11](https://langchain-ai.github.io/langgraph/how-tos/streaming-tokens/). We include in case you are running this notebook in CoLab, which will use python 3.x."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "2d7321e0-0d99-4efe-a67b-74c12271859b",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 350
        },
        "id": "2d7321e0-0d99-4efe-a67b-74c12271859b",
        "outputId": "342aa1b9-978d-4ade-dc40-da7e56666854"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "from IPython.display import Image, display\n",
        "\n",
        "from langchain_google_genai import ChatGoogleGenerativeAI\n",
        "from langchain_core.messages import SystemMessage, HumanMessage, RemoveMessage\n",
        "from langchain_core.runnables import RunnableConfig\n",
        "from langgraph.graph.state import CompiledStateGraph\n",
        "\n",
        "from langgraph.checkpoint.memory import MemorySaver\n",
        "from langgraph.graph import StateGraph, START, END\n",
        "from langgraph.graph import MessagesState\n",
        "\n",
        "# LLM\n",
        "model: ChatGoogleGenerativeAI = ChatGoogleGenerativeAI(model = \"gemini-1.5-flash\")\n",
        "\n",
        "# State\n",
        "class State(MessagesState):\n",
        "    summary: str\n",
        "\n",
        "# Define the logic to call the model\n",
        "def call_model(state: State, config: RunnableConfig):\n",
        "\n",
        "    # Get summary if it exists\n",
        "    summary = state.get(\"summary\", \"\")\n",
        "\n",
        "    # If there is summary, then we add it\n",
        "    if summary:\n",
        "\n",
        "        # Add summary to system message\n",
        "        system_message = f\"Summary of conversation earlier: {summary}\"\n",
        "\n",
        "        # Append summary to any newer messages\n",
        "        messages = [SystemMessage(content=system_message)] + state[\"messages\"]\n",
        "\n",
        "    else:\n",
        "        messages = state[\"messages\"]\n",
        "\n",
        "    response = model.invoke(messages, config)\n",
        "    return {\"messages\": response}\n",
        "\n",
        "def summarize_conversation(state: State):\n",
        "\n",
        "    # First, we get any existing summary\n",
        "    summary = state.get(\"summary\", \"\")\n",
        "\n",
        "    # Create our summarization prompt\n",
        "    if summary:\n",
        "\n",
        "        # A summary already exists\n",
        "        summary_message = (\n",
        "            f\"This is summary of the conversation to date: {summary}\\n\\n\"\n",
        "            \"Extend the summary by taking into account the new messages above:\"\n",
        "        )\n",
        "\n",
        "    else:\n",
        "        summary_message = \"Create a summary of the conversation above:\"\n",
        "\n",
        "    # Add prompt to our history\n",
        "    messages = state[\"messages\"] + [HumanMessage(content=summary_message)]\n",
        "    response = model.invoke(messages)\n",
        "\n",
        "    # Delete all but the 2 most recent messages\n",
        "    delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n",
        "    return {\"summary\": response.content, \"messages\": delete_messages}\n",
        "\n",
        "# Determine whether to end or summarize the conversation\n",
        "def should_continue(state: State):\n",
        "\n",
        "    \"\"\"Return the next node to execute.\"\"\"\n",
        "\n",
        "    messages = state[\"messages\"]\n",
        "\n",
        "    # If there are more than six messages, then we summarize the conversation\n",
        "    if len(messages) > 6:\n",
        "        return \"summarize_conversation\"\n",
        "\n",
        "    # Otherwise we can just end\n",
        "    return END\n",
        "\n",
        "# Define a new graph\n",
        "workflow: StateGraph = StateGraph(State)\n",
        "workflow.add_node(\"conversation\", call_model)\n",
        "workflow.add_node(summarize_conversation)\n",
        "\n",
        "# Set the entrypoint as conversation\n",
        "workflow.add_edge(START, \"conversation\")\n",
        "workflow.add_conditional_edges(\"conversation\", should_continue)\n",
        "workflow.add_edge(\"summarize_conversation\", END)\n",
        "\n",
        "# Compile\n",
        "memory: MemorySaver = MemorySaver()\n",
        "graph: CompiledStateGraph = workflow.compile(checkpointer=memory)\n",
        "display(Image(graph.get_graph().draw_mermaid_png()))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f847a787-b301-488c-9b58-cba9f389f55d",
      "metadata": {
        "id": "f847a787-b301-488c-9b58-cba9f389f55d"
      },
      "source": [
        "### Streaming full state\n",
        "\n",
        "Now, let's talk about ways to [stream our graph state](https://langchain-ai.github.io/langgraph/concepts/low_level/#streaming).\n",
        "\n",
        "`.stream` and `.astream` are sync and async methods for streaming back results.\n",
        "\n",
        "LangGraph supports a few [different streaming modes](https://langchain-ai.github.io/langgraph/how-tos/stream-values/) for [graph state](https://langchain-ai.github.io/langgraph/how-tos/stream-values/):\n",
        "\n",
        "* `values`: This streams the full state of the graph after each node is called.\n",
        "* `updates`: This streams updates to the state of the graph after each node is called.\n",
        "\n",
        "![values_vs_updates.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbaf892d24625a201744e5_streaming1.png)\n",
        "\n",
        "Let's look at `stream_mode=\"updates\"`.\n",
        "\n",
        "Because we stream with `updates`, we only see updates to the state after node in the graph is run.\n",
        "\n",
        "Each `chunk` is a dict with `node_name` as the key and the updated state as the value."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "9a6f8ae9-f244-40c5-a2da-618b72631b22",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "9a6f8ae9-f244-40c5-a2da-618b72631b22",
        "outputId": "8aef39c8-7ec5-44c8-fb2b-e0ce8828a3c6"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "{'conversation': {'messages': AIMessage(content=\"Hi Lance! It's nice to meet you. 😄  What can I do for you today?  Are you looking for information, want to chat, or have a specific task in mind?  Let me know! 😊 \\n\", additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-96095c4b-03ee-47a5-91d7-e2c01411c34f-0', usage_metadata={'input_tokens': 118, 'output_tokens': 46, 'total_tokens': 164})}}\n",
            "{'summarize_conversation': {'messages': [RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='77ae3850-f9da-4aae-88db-6347790e5348'), RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='run-54b96380-69c3-4a60-99ba-39b07f5eaa7f-0'), RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='a551bdfa-ddb7-40b8-b9ed-12b81383d3d6'), RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='run-6460cce4-021b-4237-9d47-23ad4b74449f-0'), RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='3d2a0a45-7121-4f52-a201-8eacbbcceea8'), RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='run-a850161d-f4fc-409b-ab5d-93f042d90d97-0')], 'summary': 'The conversation above is a simple greeting exchange. Lance introduced himself, and I responded with a greeting and an offer to help. The conversation ended with me asking Lance what he would like to do. \\n'}}\n"
          ]
        }
      ],
      "source": [
        "# Create a thread\n",
        "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
        "\n",
        "# Start conversation\n",
        "for chunk in graph.stream({\"messages\": [HumanMessage(content=\"hi! I'm Lance\")]}, config, stream_mode=\"updates\"):\n",
        "    print(chunk)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0c4882e9-07dd-4d70-866b-dfc530418cad",
      "metadata": {
        "id": "0c4882e9-07dd-4d70-866b-dfc530418cad"
      },
      "source": [
        "Let's now just print the state update."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "c859c777-cb12-4682-9108-6b367e597b81",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "c859c777-cb12-4682-9108-6b367e597b81",
        "outputId": "fa93db25-774a-46ac-f6d8-476c714abe8b"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "Hi Lance! It's nice to meet you. What can I do for you today?\n"
          ]
        }
      ],
      "source": [
        "# Start conversation\n",
        "for chunk in graph.stream({\"messages\": [HumanMessage(content=\"hi! I'm Lance\")]}, config, stream_mode=\"updates\"):\n",
        "    chunk['conversation'][\"messages\"].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "583bf219-6358-4d06-ae99-c40f43569fda",
      "metadata": {
        "id": "583bf219-6358-4d06-ae99-c40f43569fda"
      },
      "source": [
        "Now, we can see `stream_mode=\"values\"`.\n",
        "\n",
        "This is the `full state` of the graph after the `conversation` node is called."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "6ee763f8-6d1f-491e-8050-fb1439e116df",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "6ee763f8-6d1f-491e-8050-fb1439e116df",
        "outputId": "f14d2d4c-dcf5-442c-ee28-39a5b5bf8526"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "hi! I'm Lance\n",
            "---------------------------------------------------------------------------\n",
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "hi! I'm Lance\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "Hello Lance! Nice to meet you. What can I do for you today?\n",
            "---------------------------------------------------------------------------\n"
          ]
        }
      ],
      "source": [
        "# Start conversation, again\n",
        "config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
        "\n",
        "# Start conversation\n",
        "input_message = HumanMessage(content=\"hi! I'm Lance\")\n",
        "for event in graph.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
        "    for m in event['messages']:\n",
        "        m.pretty_print()\n",
        "    print(\"---\"*25)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "563c198a-d1a4-4700-b7a7-ff5b8e0b25d7",
      "metadata": {
        "id": "563c198a-d1a4-4700-b7a7-ff5b8e0b25d7"
      },
      "source": [
        "### Streaming tokens\n",
        "\n",
        "We often want to stream more than graph state.\n",
        "\n",
        "In particular, with chat model calls it is common to stream the tokens as they are generated.\n",
        "\n",
        "We can do this [using the `.astream_events` method](https://langchain-ai.github.io/langgraph/how-tos/streaming-from-final-node/#stream-outputs-from-the-final-node), which streams back events as they happen inside nodes!\n",
        "\n",
        "Each event is a dict with a few keys:\n",
        "\n",
        "* `event`: This is the type of event that is being emitted.\n",
        "* `name`: This is the name of event.\n",
        "* `data`: This is the data associated with the event.\n",
        "* `metadata`: Contains`langgraph_node`, the node emitting the event.\n",
        "\n",
        "Let's have a look."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "6ae8c7a6-c6e7-4cef-ac9f-190d2f4dd763",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "6ae8c7a6-c6e7-4cef-ac9f-190d2f4dd763",
        "outputId": "d5a47429-65cc-4c2c-a32c-f9a08b8d0c54"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Node: . Type: on_chain_start. Name: LangGraph\n",
            "Node: __start__. Type: on_chain_start. Name: __start__\n",
            "Node: __start__. Type: on_chain_end. Name: __start__\n",
            "Node: conversation. Type: on_chain_start. Name: conversation\n",
            "Node: conversation. Type: on_chat_model_start. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chat_model_end. Name: ChatGoogleGenerativeAI\n",
            "Node: conversation. Type: on_chain_start. Name: _write\n",
            "Node: conversation. Type: on_chain_end. Name: _write\n",
            "Node: conversation. Type: on_chain_start. Name: should_continue\n",
            "Node: conversation. Type: on_chain_end. Name: should_continue\n",
            "Node: conversation. Type: on_chain_stream. Name: conversation\n",
            "Node: conversation. Type: on_chain_end. Name: conversation\n",
            "Node: . Type: on_chain_stream. Name: LangGraph\n",
            "Node: . Type: on_chain_end. Name: LangGraph\n"
          ]
        }
      ],
      "source": [
        "config = {\"configurable\": {\"thread_id\": \"3\"}}\n",
        "\n",
        "input_message = HumanMessage(content=\"Tell me about the 49ers NFL team\")\n",
        "\n",
        "async for event in graph.astream_events({\"messages\": [input_message]}, config, version=\"v2\"):\n",
        "\n",
        "    print(f\"Node: {event['metadata'].get('langgraph_node','')}. Type: {event['event']}. Name: {event['name']}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0b63490f-3d24-4f68-95ca-5320ccb61d2d",
      "metadata": {
        "id": "0b63490f-3d24-4f68-95ca-5320ccb61d2d"
      },
      "source": [
        "The central point is that tokens from chat models within your graph have the `on_chat_model_stream` type.\n",
        "\n",
        "We can use `event['metadata']['langgraph_node']` to select the node to stream from.\n",
        "\n",
        "And we can use `event['data']` to get the actual data for each event, which in this case is an `AIMessageChunk`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "cc3529f8-3960-4d41-9ed6-373f93183950",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "cc3529f8-3960-4d41-9ed6-373f93183950",
        "outputId": "31bdc2e2-ce2c-48ec-bb76-6183e9ab0ac8"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "{'chunk': AIMessageChunk(content='## The', additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': []}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 2, 'total_tokens': 13})}\n",
            "{'chunk': AIMessageChunk(content=' San Francisco 49ers: A Legacy of Success and Grit\\n\\nThe San Francisco 4', additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 21, 'total_tokens': 32})}\n",
            "{'chunk': AIMessageChunk(content='9ers are one of the most storied franchises in NFL history, boasting a', additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 37, 'total_tokens': 48})}\n",
            "{'chunk': AIMessageChunk(content=\" rich legacy of success, iconic players, and passionate fans. Here's a glimpse into their story:\\n\\n**Early Days and Rise to Greatness:**\\n\\n\", additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 68, 'total_tokens': 79})}\n",
            "{'chunk': AIMessageChunk(content='* **Founded in 1946:** The 49ers were one of the original 11 teams that formed the All-America Football Conference (', additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 101, 'total_tokens': 112})}\n",
            "{'chunk': AIMessageChunk(content='AAFC). They joined the NFL in 1950.\\n* **The \"Golden Era\":** The 1980s marked a golden age for the franchise. Led by legendary coach Bill Walsh, the 49ers', additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 151, 'total_tokens': 162})}\n",
            "{'chunk': AIMessageChunk(content=' won four Super Bowls in a span of 13 years (1982, 1985, 1989, 1995).\\n* **Iconic Players:** This era featured legendary players like', additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 199, 'total_tokens': 210})}\n",
            "{'chunk': AIMessageChunk(content=' Joe Montana, Jerry Rice, Steve Young, Ronnie Lott, and Charles Haley, who are considered some of the greatest to ever play the game.\\n\\n**Recent Years and Current Status:**\\n\\n* **Post-Super Bowl Success:** After a period of relative decline, the 49ers experienced a resurgence in the late 2', additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 265, 'total_tokens': 276})}\n",
            "{'chunk': AIMessageChunk(content='010s and early 2020s. They reached the Super Bowl in 2019 and 2020, losing both times.\\n* **Current Roster:** The 49ers currently have a young and talented roster, featuring players like Trey Lance, Deebo Samuel,', additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 329, 'total_tokens': 340})}\n",
            "{'chunk': AIMessageChunk(content=\" George Kittle, and Fred Warner.\\n* **Head Coach Kyle Shanahan:** The team is led by head coach Kyle Shanahan, known for his innovative offensive schemes and ability to develop young talent.\\n\\n**Key Facts:**\\n\\n* **Home Stadium:** Levi's Stadium in Santa Clara, California.\\n*\", additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 393, 'total_tokens': 404})}\n",
            "{'chunk': AIMessageChunk(content=\" **Mascot:** Sourdough Sam.\\n* **Colors:** Red, gold, and black.\\n* **Rivals:** Seattle Seahawks, Los Angeles Rams, and Arizona Cardinals.\\n\\n**The 49ers are more than just a football team; they are a symbol of San Francisco's spirit and a testament to the\", additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 460, 'total_tokens': 471})}\n",
            "{'chunk': AIMessageChunk(content=' enduring legacy of the game.** Their fans are known for their passion and unwavering support, making every game a thrilling experience. \\n\\n**To delve deeper, you can explore:**\\n\\n* **Official website:** www.49ers.com\\n* **Wikipedia:** https://en.wikipedia.org/wiki/San_Francisco_', additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 528, 'total_tokens': 539})}\n",
            "{'chunk': AIMessageChunk(content=\"49ers\\n* **NFL.com:** https://www.nfl.com/teams/san-francisco-49ers\\n\\nWhether you're a lifelong fan or just starting to learn about the team, the San Francisco 49ers offer a compelling story of success, resilience, and the enduring power of the\", additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 595, 'total_tokens': 606})}\n",
            "{'chunk': AIMessageChunk(content=' game. \\n', additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-ce8c38fe-970a-4e17-83ca-8230f92246ce', usage_metadata={'input_tokens': 11, 'output_tokens': 597, 'total_tokens': 608})}\n"
          ]
        }
      ],
      "source": [
        "node_to_stream = 'conversation'\n",
        "config = {\"configurable\": {\"thread_id\": \"4\"}}\n",
        "input_message = HumanMessage(content=\"Tell me about the 49ers NFL team\")\n",
        "async for event in graph.astream_events({\"messages\": [input_message]}, config, version=\"v2\"):\n",
        "    # Get chat model tokens from a particular node\n",
        "    if event[\"event\"] == \"on_chat_model_stream\" and event['metadata'].get('langgraph_node','') == node_to_stream:\n",
        "        print(event[\"data\"])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "226e569a-76c3-43d8-8f89-3ae687efde1c",
      "metadata": {
        "id": "226e569a-76c3-43d8-8f89-3ae687efde1c"
      },
      "source": [
        "As you see above, just use the `chunk` key to get the `AIMessageChunk`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "3aeae53d-6dcf-40d0-a0c6-c40de492cc83",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "3aeae53d-6dcf-40d0-a0c6-c40de492cc83",
        "outputId": "997579e3-aaa7-4455-cc9f-7e39bcb8addd"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "## The| San Francisco 49ers: A Legacy of Excellence\n",
            "\n",
            "The San Francisco 49|ers are one of the most storied franchises in NFL history, boasting a rich| legacy of success and a passionate fan base. Here's a glimpse into their world:\n",
            "\n",
            "**History:**\n",
            "\n",
            "* **Founded:** 1946,| originally as the \"Golden Gate 49ers\" in the All-America Football Conference (AAFC).\n",
            "* **Joined NFL:** 1950,| after the AAFC folded.\n",
            "* **Home Stadium:** Levi's Stadium (Santa Clara, California) since 2014. Previously played at Candlestick Park (1971-2014) and Kezar| Stadium (1946-1970).\n",
            "* **Team Colors:** Gold, scarlet red, and black.\n",
            "* **Mascot:** \"Sourdough Sam,\" a sourdough bread baker.\n",
            "\n",
            "**Achievements:**\n",
            "\n",
            "*| **Super Bowl Championships (5):** Super Bowl XVI (1982), XIX (1985), XXIII (1989), XXIX (1995), and XXXIV (2000).\n",
            "* **NFL Championships (3):** 1957, 19|81, 1989.\n",
            "* **NFC Championships (16):** 1971, 1981, 1982, 1984, 1985, 1988, 1989, 1990|, 1992, 1994, 1997, 2011, 2012, 2013, 2019, and 2022.\n",
            "* **Hall of Famers:**  Numerous, including Joe Montana,| Jerry Rice, Steve Young, Ronnie Lott, Deion Sanders, and many more.\n",
            "\n",
            "**Recent Years:**\n",
            "\n",
            "* The 49ers have experienced a recent resurgence, making it to the Super Bowl in 2020 and the NFC Championship game in 2022. \n",
            "* **Current| Head Coach:** Kyle Shanahan\n",
            "* **Notable Players:**  George Kittle, Deebo Samuel, Nick Bosa, Fred Warner, Trey Lance, Christian McCaffrey.\n",
            "\n",
            "**Culture:**\n",
            "\n",
            "* Known for their strong defense and innovative offensive schemes.\n",
            "* The team has a strong connection to the Bay Area and| its diverse communities.\n",
            "* The \"Faithful\" fanbase is renowned for their passion and dedication.\n",
            "\n",
            "**The 49ers are a team rich in history and tradition, with a bright future ahead. Their commitment to excellence and loyal fanbase make them one of the most exciting and beloved teams in the NFL.** \n",
            "|"
          ]
        }
      ],
      "source": [
        "config = {\"configurable\": {\"thread_id\": \"5\"}}\n",
        "input_message = HumanMessage(content=\"Tell me about the 49ers NFL team\")\n",
        "async for event in graph.astream_events({\"messages\": [input_message]}, config, version=\"v2\"):\n",
        "    # Get chat model tokens from a particular node\n",
        "    if event[\"event\"] == \"on_chat_model_stream\" and event['metadata'].get('langgraph_node','') == node_to_stream:\n",
        "        data = event[\"data\"]\n",
        "        print(data[\"chunk\"].content, end=\"|\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5826e4d8-846b-4f6c-a5c1-e781d43022db",
      "metadata": {
        "id": "5826e4d8-846b-4f6c-a5c1-e781d43022db"
      },
      "source": [
        "### Streaming with LangGraph API\n",
        "\n",
        "--\n",
        "\n",
        "**⚠️ DISCLAIMER**\n",
        "\n",
        "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n",
        "\n",
        "*Also, if you are running this notebook in CoLab, then skip this step.*\n",
        "\n",
        "--\n",
        "\n",
        "The LangGraph API [has first class support for streaming](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#streaming).\n",
        "\n",
        "Let's load our `agent` in the Studio UI, which uses `module-3/studio/agent.py` set in `module-3/studio/langgraph.json`.\n",
        "\n",
        "The LangGraph API serves as the back-end for Studio.\n",
        "\n",
        "We can interact directly with the LangGraph API via the LangGraph SDK.\n",
        "\n",
        "We just need to get the URL for the local deployment from Studio.\n",
        "\n",
        "![Screenshot 2024-08-27 at 2.20.34 PM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbaf8943c3d4df239cbf0f_streaming2.png)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "079c2ad6",
      "metadata": {
        "id": "079c2ad6"
      },
      "outputs": [],
      "source": [
        "from langgraph_sdk import get_client\n",
        "\n",
        "# Replace this with the URL of your own deployed graph\n",
        "URL = \"http://localhost:56091\"\n",
        "client = get_client(url=URL)\n",
        "\n",
        "# Search all hosted graphs\n",
        "assistants = await client.assistants.search()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4d15af9e-0e86-41e3-a5ba-ee2a4aa08a32",
      "metadata": {
        "id": "4d15af9e-0e86-41e3-a5ba-ee2a4aa08a32"
      },
      "source": [
        "Let's [stream `values`](https://langchain-ai.github.io/langgraph/cloud/how-tos/stream_values/), like before."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "63e3096f-5429-4d3c-8de2-2bddf7266ebf",
      "metadata": {
        "id": "63e3096f-5429-4d3c-8de2-2bddf7266ebf",
        "outputId": "bb18d884-8366-4158-e696-437661418558"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "StreamPart(event='metadata', data={'run_id': '1ef6a3d0-41eb-66f4-a311-8ebdfa1b281f'})\n",
            "StreamPart(event='values', data={'messages': [{'content': 'Multiply 2 and 3', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '345c67cf-c958-4f89-b787-540fc025080c', 'example': False}]})\n",
            "StreamPart(event='values', data={'messages': [{'content': 'Multiply 2 and 3', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '345c67cf-c958-4f89-b787-540fc025080c', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-88179a6d-eb1e-4953-ac42-0b533b6d76f6', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}]})\n",
            "StreamPart(event='values', data={'messages': [{'content': 'Multiply 2 and 3', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '345c67cf-c958-4f89-b787-540fc025080c', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-88179a6d-eb1e-4953-ac42-0b533b6d76f6', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': '6', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': '4dd5ce10-ac0b-4a91-b34b-c35109dcbf29', 'tool_call_id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'artifact': None, 'status': 'success'}]})\n",
            "StreamPart(event='values', data={'messages': [{'content': 'Multiply 2 and 3', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '345c67cf-c958-4f89-b787-540fc025080c', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-88179a6d-eb1e-4953-ac42-0b533b6d76f6', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': '6', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': '4dd5ce10-ac0b-4a91-b34b-c35109dcbf29', 'tool_call_id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'artifact': None, 'status': 'success'}, {'content': 'The result of multiplying 2 and 3 is 6.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-b5862486-a25f-48fc-9a03-a8506a6692a8', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]})\n"
          ]
        }
      ],
      "source": [
        "# Create a new thread\n",
        "thread = await client.threads.create()\n",
        "# Input message\n",
        "input_message = HumanMessage(content=\"Multiply 2 and 3\")\n",
        "async for event in client.runs.stream(thread[\"thread_id\"],\n",
        "                                      assistant_id=\"agent\",\n",
        "                                      input={\"messages\": [input_message]},\n",
        "                                      stream_mode=\"values\"):\n",
        "    print(event)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "556dc7fd-1cae-404f-816a-f13d772b3b14",
      "metadata": {
        "id": "556dc7fd-1cae-404f-816a-f13d772b3b14"
      },
      "source": [
        "The streamed objects have:\n",
        "\n",
        "* `event`: Type\n",
        "* `data`: State"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "57b735aa-139c-45a3-a850-63519c0004f0",
      "metadata": {
        "id": "57b735aa-139c-45a3-a850-63519c0004f0",
        "outputId": "45769b8a-1f9c-463e-ced2-857b323ea49f"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "=========================\n",
            "content='Multiply 2 and 3' additional_kwargs={'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'example': False} id='f51807de-6b99-4da4-a798-26cf59d16412'\n",
            "=========================\n",
            "content='' additional_kwargs={'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_imZHAw7kvMR2ZeKaQVSlj25C', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'example': False, 'invalid_tool_calls': [], 'usage_metadata': None} id='run-fa4ab1c6-274d-4be5-8c4a-a6411c7c35cc' tool_calls=[{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_imZHAw7kvMR2ZeKaQVSlj25C', 'type': 'tool_call'}]\n",
            "=========================\n",
            "content='6' additional_kwargs={'additional_kwargs': {}, 'response_metadata': {}, 'status': 'success'} name='multiply' id='3e7bbfb6-aa82-453a-969c-9c753fbd1d74' tool_call_id='call_imZHAw7kvMR2ZeKaQVSlj25C'\n",
            "=========================\n",
            "content='The result of multiplying 2 and 3 is 6.' additional_kwargs={'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'example': False, 'invalid_tool_calls': [], 'usage_metadata': None} id='run-e8e0d672-cfb2-42be-850a-345df3718f69'\n",
            "=========================\n"
          ]
        }
      ],
      "source": [
        "from langchain_core.messages import convert_to_messages\n",
        "thread = await client.threads.create()\n",
        "input_message = HumanMessage(content=\"Multiply 2 and 3\")\n",
        "async for event in client.runs.stream(thread[\"thread_id\"], assistant_id=\"agent\", input={\"messages\": [input_message]}, stream_mode=\"values\"):\n",
        "    messages = event.data.get('messages',None)\n",
        "    if messages:\n",
        "        print(convert_to_messages(messages)[-1])\n",
        "    print('='*25)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a555d186-27be-4ddf-934c-895a3105035d",
      "metadata": {
        "id": "a555d186-27be-4ddf-934c-895a3105035d"
      },
      "source": [
        "There are some new streaming mode that are only supported via the API.\n",
        "\n",
        "For example, we can [use `messages` mode](https://langchain-ai.github.io/langgraph/cloud/how-tos/stream_messages/) to better handle the above case!\n",
        "\n",
        "This mode currently assumes that you have a `messages` key in your graph, which is a list of messages.\n",
        "\n",
        "All events emitted using `messages` mode have two attributes:\n",
        "\n",
        "* `event`: This is the name of the event\n",
        "* `data`: This is data associated with the event"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4abd91f6-63c0-41ee-9988-7c8248b88a45",
      "metadata": {
        "id": "4abd91f6-63c0-41ee-9988-7c8248b88a45",
        "outputId": "fdf211ba-bcac-4028-c073-6c0078e42fd3"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "metadata\n",
            "messages/complete\n",
            "messages/metadata\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/complete\n",
            "messages/complete\n",
            "messages/metadata\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/partial\n",
            "messages/complete\n"
          ]
        }
      ],
      "source": [
        "thread = await client.threads.create()\n",
        "input_message = HumanMessage(content=\"Multiply 2 and 3\")\n",
        "async for event in client.runs.stream(thread[\"thread_id\"],\n",
        "                                      assistant_id=\"agent\",\n",
        "                                      input={\"messages\": [input_message]},\n",
        "                                      stream_mode=\"messages\"):\n",
        "    print(event.event)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8de2f1ea-b232-43fc-af7a-320efce83381",
      "metadata": {
        "id": "8de2f1ea-b232-43fc-af7a-320efce83381"
      },
      "source": [
        "We can see a few events:\n",
        "\n",
        "* `metadata`: metadata about the run\n",
        "* `messages/complete`: fully formed message\n",
        "* `messages/partial`: chat model tokens\n",
        "\n",
        "You can dig further into the types [here](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#modemessages).\n",
        "\n",
        "Now, let's show how to stream these messages.\n",
        "\n",
        "We'll define a helper function for better formatting of the tool calls in messages."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "50a85e16-6e3f-4f14-bcf9-8889a762f522",
      "metadata": {
        "id": "50a85e16-6e3f-4f14-bcf9-8889a762f522",
        "outputId": "db5a7af9-caa8-4251-aa35-52d25a2c3e35"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Metadata: Run ID - 1ef6a3da-687f-6253-915a-701de5327165\n",
            "--------------------------------------------------\n",
            "Tool Calls:\n",
            "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {}\n",
            "--------------------------------------------------\n",
            "Tool Calls:\n",
            "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {}\n",
            "--------------------------------------------------\n",
            "Tool Calls:\n",
            "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {}\n",
            "--------------------------------------------------\n",
            "Tool Calls:\n",
            "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {}\n",
            "--------------------------------------------------\n",
            "Tool Calls:\n",
            "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2}\n",
            "--------------------------------------------------\n",
            "Tool Calls:\n",
            "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2}\n",
            "--------------------------------------------------\n",
            "Tool Calls:\n",
            "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2}\n",
            "--------------------------------------------------\n",
            "Tool Calls:\n",
            "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2}\n",
            "--------------------------------------------------\n",
            "Tool Calls:\n",
            "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2, 'b': 3}\n",
            "--------------------------------------------------\n",
            "Tool Calls:\n",
            "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2, 'b': 3}\n",
            "--------------------------------------------------\n",
            "Tool Calls:\n",
            "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2, 'b': 3}\n",
            "Response Metadata: Finish Reason - tool_calls\n",
            "--------------------------------------------------\n",
            "--------------------------------------------------\n",
            "AI: The\n",
            "--------------------------------------------------\n",
            "AI: The result\n",
            "--------------------------------------------------\n",
            "AI: The result of\n",
            "--------------------------------------------------\n",
            "AI: The result of multiplying\n",
            "--------------------------------------------------\n",
            "AI: The result of multiplying \n",
            "--------------------------------------------------\n",
            "AI: The result of multiplying 2\n",
            "--------------------------------------------------\n",
            "AI: The result of multiplying 2 and\n",
            "--------------------------------------------------\n",
            "AI: The result of multiplying 2 and \n",
            "--------------------------------------------------\n",
            "AI: The result of multiplying 2 and 3\n",
            "--------------------------------------------------\n",
            "AI: The result of multiplying 2 and 3 is\n",
            "--------------------------------------------------\n",
            "AI: The result of multiplying 2 and 3 is \n",
            "--------------------------------------------------\n",
            "AI: The result of multiplying 2 and 3 is 6\n",
            "--------------------------------------------------\n",
            "AI: The result of multiplying 2 and 3 is 6.\n",
            "--------------------------------------------------\n",
            "AI: The result of multiplying 2 and 3 is 6.\n",
            "Response Metadata: Finish Reason - stop\n",
            "--------------------------------------------------\n"
          ]
        }
      ],
      "source": [
        "thread = await client.threads.create()\n",
        "input_message = HumanMessage(content=\"Multiply 2 and 3\")\n",
        "\n",
        "def format_tool_calls(tool_calls):\n",
        "    \"\"\"\n",
        "    Format a list of tool calls into a readable string.\n",
        "\n",
        "    Args:\n",
        "        tool_calls (list): A list of dictionaries, each representing a tool call.\n",
        "            Each dictionary should have 'id', 'name', and 'args' keys.\n",
        "\n",
        "    Returns:\n",
        "        str: A formatted string of tool calls, or \"No tool calls\" if the list is empty.\n",
        "\n",
        "    \"\"\"\n",
        "\n",
        "    if tool_calls:\n",
        "        formatted_calls = []\n",
        "        for call in tool_calls:\n",
        "            formatted_calls.append(\n",
        "                f\"Tool Call ID: {call['id']}, Function: {call['name']}, Arguments: {call['args']}\"\n",
        "            )\n",
        "        return \"\\n\".join(formatted_calls)\n",
        "    return \"No tool calls\"\n",
        "\n",
        "async for event in client.runs.stream(\n",
        "    thread[\"thread_id\"],\n",
        "    assistant_id=\"agent\",\n",
        "    input={\"messages\": [input_message]},\n",
        "    stream_mode=\"messages\",):\n",
        "\n",
        "    # Handle metadata events\n",
        "    if event.event == \"metadata\":\n",
        "        print(f\"Metadata: Run ID - {event.data['run_id']}\")\n",
        "        print(\"-\" * 50)\n",
        "\n",
        "    # Handle partial message events\n",
        "    elif event.event == \"messages/partial\":\n",
        "        for data_item in event.data:\n",
        "            # Process user messages\n",
        "            if \"role\" in data_item and data_item[\"role\"] == \"user\":\n",
        "                print(f\"Human: {data_item['content']}\")\n",
        "            else:\n",
        "                # Extract relevant data from the event\n",
        "                tool_calls = data_item.get(\"tool_calls\", [])\n",
        "                invalid_tool_calls = data_item.get(\"invalid_tool_calls\", [])\n",
        "                content = data_item.get(\"content\", \"\")\n",
        "                response_metadata = data_item.get(\"response_metadata\", {})\n",
        "\n",
        "                if content:\n",
        "                    print(f\"AI: {content}\")\n",
        "\n",
        "                if tool_calls:\n",
        "                    print(\"Tool Calls:\")\n",
        "                    print(format_tool_calls(tool_calls))\n",
        "\n",
        "                if invalid_tool_calls:\n",
        "                    print(\"Invalid Tool Calls:\")\n",
        "                    print(format_tool_calls(invalid_tool_calls))\n",
        "\n",
        "                if response_metadata:\n",
        "                    finish_reason = response_metadata.get(\"finish_reason\", \"N/A\")\n",
        "                    print(f\"Response Metadata: Finish Reason - {finish_reason}\")\n",
        "\n",
        "        print(\"-\" * 50)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "1ae885f8-102f-448a-9d68-8ded8d2bbd18",
      "metadata": {
        "id": "1ae885f8-102f-448a-9d68-8ded8d2bbd18"
      },
      "outputs": [],
      "source": []
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3 (ipykernel)",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12.6"
    },
    "colab": {
      "provenance": []
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}